package com.hup.sokoban.model.common;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hup.sokoban.constant.MapConstant;
import com.hup.sokoban.constant.MapConstant.ElementType;
import com.hup.utils.algorithm.image.MagicWand.Helper;
import com.hup.utils.algorithm.image.MagicWand.ThresholdHandler;
import com.hup.utils.commons.DateBuilder;
import com.hup.utils.commons.exception.AppCommonException;
import com.hup.utils.commons.exception.AppExceptionHandler;
import com.hup.utils.commons.exception.CauseException;
import com.hup.utils.commons.exception.ErrInfo;
import com.hup.utils.commons.io.FileUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;

import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;

/**
 * 关卡数据
 *
 * @author hugan
 * @date 2020/4/29
 */
@Getter
@Log4j2
public class MapItem {

    /**
     * 文件路径,读取文件夹时更新;
     * 外部作用:作为item匹配的标志,删除文件时使用
     */
    @Getter
    private transient String path;
    /**
     * 文件名,不含扩展名,读取文件夹时更新,未修改时等于name;
     */
    private transient String fileName;
    /**
     * 最后修改时间,读取文件夹时更新;
     */
    @Getter
    private transient long lastModified;

    /**
     * 如程序默认是[关卡n],允许用户修改
     */
    @Setter
    private String name;
    @Setter
    private String description;
    /**
     * 用户设置的背景图像:null-使用内置背景图,notNull-用户设置的背景路径
     */
    @Setter
    private String backgroundImage;
    /**
     * 锁定背景比例
     */
    @Setter
    private boolean lockBgScale;
    /**
     * 宽度/列数
     */
    @Setter
    private int width;
    /**
     * 高度/行数
     */
    @Setter
    private int height;
    /**
     * 地图数据;
     * {@link ElementType}
     */
    @Setter
    private int[] map;

    /**
     * [地图未完成]的信息
     */
    private transient String notSafeMsg;
    /**
     * 阿狸所在的区域
     */
    private transient ArrayList<Integer> aliArea = new ArrayList<>();

    @Getter
    private transient boolean playing;
    @Getter
    private transient boolean winning;

    public static MapItem getDefault() {
        MapItem res = new MapItem();
        res.name = "起个啥名";
        res.width = 20;
        res.height = 12;
        res.map = new int[res.width * res.height];
        //居中的默认元素
        int centerI = res.width * res.height / 2 + res.width / 2;
        res.map[centerI - res.width] = ElementType.ALI_FRONT.intValue;
        res.map[centerI] = ElementType.BIRD.intValue;
        res.map[centerI + res.width] = ElementType.CAGE.intValue;
        res.lockBgScale = true;
        return res;
    }

    public MapItem getClone() {
        MapItem res = new MapItem();
        res.path = path;
        res.fileName = fileName;
        res.lastModified = lastModified;
        res.name = name;
        res.description = description;
        res.backgroundImage = backgroundImage;
        res.lockBgScale = lockBgScale;
        res.width = width;
        res.height = height;
        res.map = new int[map.length];
        res.notSafeMsg = notSafeMsg;
        res.aliArea.addAll(aliArea);
        System.arraycopy(map, 0, res.map, 0, map.length);
        return res;
    }

    /**
     * @param mapFile 必须是有效[文件名,文件数据]的文件
     */
    public static MapItem readFromFile(File mapFile) {
        MapItem res;
        try {
            String str = FileUtils.readFileToString(mapFile, StandardCharsets.UTF_8);
            res = JSON.parseObject(str, MapItem.class);
            res.aliArea = new ArrayList<>();//读到的数据中,transient是null
            if (res.width * res.height != res.map.length) {
                throw new IllegalArgumentException("关卡数据异常");
            }
        } catch (Exception e) {
            res = getDefault();
            res.name = mapFile.getName();//需要初始化名字!
            ErrInfo errInfo = AppExceptionHandler.handle("解析错误:", e);
            res.description = errInfo.getErrMsg();
        }
        res.path = mapFile.getPath();
        res.lastModified = mapFile.lastModified();
        res.fileName = mapFile.getName();
        int ed = res.fileName.lastIndexOf(MapConstant.EXT_DESIGNING);
        if (ed == -1) {
            ed = res.fileName.lastIndexOf(MapConstant.EXT);
        }
        //用户不修改本地文件时,总是会和name一样的
        res.fileName = res.fileName.substring(0, ed);
        res.refreshRecordsState();
        MapSafeMethods.handleMapSafe(res);
        return res;
    }

    /**
     * 调用前要执行 {@link MapItem#updateMapSafeFields()}
     */
    public void doSaveFile() {
        File parentDir = new File(MapConstant.MAP_DIR);
        if (!parentDir.exists() && !parentDir.mkdirs()) {
            throw new IllegalArgumentException("文件夹创建错误:" + parentDir.getAbsolutePath());
        }
        boolean mapSafe = notSafeMsg == null;
        File toSave = new File(MapConstant.MAP_DIR + name + (mapSafe ? MapConstant.EXT : MapConstant.EXT_DESIGNING));
        File otherType = new File(MapConstant.MAP_DIR + name + (!mapSafe ? MapConstant.EXT : MapConstant.EXT_DESIGNING));
        File originalToDelete = null;//解决name被修改的逻辑

        if (fileName == null) { //新建文件时,不允许关卡名重复
            log.info("首次创建文件");
            if (toSave.exists() || otherType.exists()) {
                throw new AppCommonException("已存在[" + name + "]关卡");
            }
        } else {
            if (!name.equals(fileName)) {
                //改了名字
                if (toSave.exists() || otherType.exists()) {
                    //拦截把名字改成已有的其他关卡
                    throw new AppCommonException("已存在[" + name + "]关卡");
                }
                originalToDelete = new File(path);
                if (!originalToDelete.exists()) originalToDelete = null;
            }
            log.info("保存修改");
        }
        FileUtil.assertFileName(name);

        //存储本类时,json比Serializable占用空间更少
        String jsonString = JSON.toJSONString(this, SerializerFeature.IgnoreNonFieldGetter, SerializerFeature.PrettyFormat);
        try (FileOutputStream fos = new FileOutputStream(toSave)) {
            fos.write(jsonString.getBytes(StandardCharsets.UTF_8));
            if (path != null && otherType.exists()) {
                //保存已有文件后,删除(如果存在的)另一种格式的文件
                boolean deleteRes = otherType.delete();
                if (!deleteRes) log.error("删除[{}]失败?", otherType.getAbsolutePath());
            }
            if (originalToDelete != null) {
                boolean deleteRes = originalToDelete.delete();
                if (!deleteRes) {
                    log.error("删除[{}]失败?", originalToDelete.getAbsolutePath());
                } else {
                    log.info("删除改名前的文件:[{}]", originalToDelete.getAbsolutePath());
                }
            }
            //向path赋值
            path = toSave.getPath();
            fileName = name;
            lastModified = toSave.lastModified();
            log.info("已写入文件,[{}], {}", path, DateBuilder.yyMMdd_HHmmss(new Date(lastModified)));
        } catch (IOException e) {
            throw new CauseException("写入文件错误:", e);
        }
    }

    /**
     * 清空[设计中会变化]的属性
     */
    public void resetDesigningFields() {
        notSafeMsg = null;
        aliArea.clear();
    }

    public void updateMapSafeFields() {
        MapSafeMethods.handleMapSafe(this);
    }

    /**
     * 刷新[游戏记录]的状态:通关记录,游戏进度
     */
    public void refreshRecordsState() {
        playing = new File(MapConstant.MAP_DIR + name + MapConstant.EXT_PROGRESS).exists();
        winning = new File(MapConstant.MAP_DIR + name + MapConstant.EXT_WINNING).exists();
    }

    @Override
    public String toString() {
        return "MapItem{" +
                "path='" + path + '\'' +
                ", lastModified=" + lastModified +
                ", width=" + width +
                ", height=" + height +
                '}';
    }

    private static class MapSafeMethods {
        /**
         * 检查地图是否为[已完成];并更新 {@link MapItem#notSafeMsg}, {@link MapItem#aliArea}
         */
        private static void handleMapSafe(MapItem mapItem) {
            String notSafeMsg = null;
            Point ali = findUniqueAli(mapItem);
            if (ali != null) {
                int[] map = mapItem.getMap();
                ArrayList<Integer> areaOfAli = findAreaOfAli(map, mapItem.getWidth(), ali);
                mapItem.aliArea.clear();
                mapItem.aliArea.addAll(areaOfAli);

                //areaOfAli 不能在地图边缘
                int width = mapItem.getWidth();
                int height = mapItem.getHeight();
                for (Integer i : areaOfAli) {
                    int x = i % width;
                    int y = i / width;
                    if (x == 0 || y == 0 || x == width - 1 || y == height - 1) {
                        notSafeMsg = "阿狸所在区域在地图边缘";
                        break;
                    }
                }

                if (notSafeMsg == null) {
                    int cageCount = 0;
                    int birdCount = 0;
                    int emptyCage = 0;
                    for (Integer index : areaOfAli) {
                        if (ElementType.hasCage(map[index])) {
                            cageCount++;
                        }
                        ElementType layer1 = ElementType.getLayer1(map[index]);
                        if (layer1.intValue == ElementType.BIRD.intValue) {
                            birdCount++;
                        }
                        if (map[index] == ElementType.CAGE.intValue || map[index] == ElementType.CAGE_ALI_FRONT.intValue) {
                            //空笼子,或者笼子带阿狸
                            emptyCage++;
                        }
                    }
                    if (cageCount != birdCount) {
                        notSafeMsg = "笼子和小鸟的数量不相等";
                    } else if (cageCount == 0) {
                        notSafeMsg = "笼子和小鸟的数量为零";
                    } else if (emptyCage == 0) {
                        notSafeMsg = "空笼子数量为零";
                    }
                }
            } else {
                notSafeMsg = "阿狸数量不等于1";
            }

            mapItem.notSafeMsg = notSafeMsg;
        }

        /**
         * @return 唯一的阿狸坐标
         */
        private static Point findUniqueAli(MapItem mapItem) {
            int width = mapItem.getWidth();
            int[] map = mapItem.getMap();
            Point res = null;
            for (int i = 0; i < map.length; i++) {
                int val = map[i];
                if (ElementType.isAli(val)) {
                    int col = i % width;
                    int row = i / width;
                    log.debug("res=[{},{}], i={}, width={}", col, row, i, width);
                    if (res == null) {
                        res = new Point(col, row);
                    } else {
                        //第二次找到阿狸就返回null
                        return null;
                    }
                }
            }
            return res;
        }

        /**
         * @return 阿狸所在的区域
         */
        private static ArrayList<Integer> findAreaOfAli(int[] map, int mapWidth, Point ali) {
            boolean[] area = Helper.create()
                    .isEight(false)
                    .thresholdHandler(new ThresholdHandler() {
                        @Override
                        public boolean matchThreshold(int targetValue, int seedValue) {
                            ElementType layer1 = ElementType.getLayer1(targetValue);
                            return layer1 != ElementType.WALL;//不是[墙],就是阿狸所在的区域
                        }
                    })
                    .init(map, mapWidth)
                    .getBoolean(ali.x, ali.y);

            ArrayList<Integer> res = new ArrayList<>();
            for (int i = 0; i < area.length; i++) {
                if (area[i]) res.add(i);
            }
            log.debug("res.size={}", res.size());
            return res;
        }
    }

}
